/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.math;


/**
 * This class is used to encode and decode floating-point values into characters
 * and vice versa. Therefore it provides generic methods. Additionally, it
 * defines the set of alloed characters, which are used for encoding and can be
 * sent to the server within say-messages.
 */
public class NumberConverter {

	/**
	 * This array holds all allowed characters, which can be used to encode
	 * say-messages. Up to now, these are 73 different characters. Note that
	 * the order of them corresponds to that of their integer-values, which
	 * should not be changed.
	 */
	public static final char[] digits = { '(', ')', '*', '+', '-', '.', '/',
			'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '<', '>', '?',
			'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
			'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
			'_', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
			'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y',
			'z' }; // 73 characters (without space)

	// private static char space = ' ';

	/**
	 * Lookup the index of a charcter within the digits array.
	 * 
	 * @param c - the corresponding character.
	 * 
	 * @return the characters index in the "digits"-array
	 */
	public static int getIndexOf(char c) {

		for (int i = 0; i < digits.length; i++)
			if (c == digits[i])
				return i;
		return -1;
	}

	/**
	 * Encodes an array of doubles to an array of characters. This is a flexible
	 * approach which adapts the accuracy to the used capacity. Each double
	 * value has to be given as a percentage of the max value and must therefore
	 * be in the range 0..1. i.e if you wan't to decode a ball speed of 1.35
	 * with max ball speed 2.7 use the value 0.5!
	 * 
	 * 
	 * @param doubles -
	 *            array of doubles. each double stands for a values percentage
	 *            of the maximal value of its kind that can be code
	 * @param numberChars -
	 *            capacity of the resulting char-array (proportional to
	 *            accuracy) Attention: this value shouldn't be higher than 5 as
	 *            higher values lead to wrong results because of the limits for
	 *            Integers.
	 * @return - array of chars which encode the double-values
	 */
	public static char[] convertToChars(double[] doubles, int numberChars) {

		for (int i = 0; i < doubles.length; i++) {

			// doubles represent percentages -> only 0..1
			if (doubles[i] < 0 || doubles[i] > 1) {
				throw new RuntimeException(" got wrong percentage:"
						+ doubles[i] + " . should be between 0..1.");
			}

			// as (1.0 * n) % n == (0 * n) % n, we have to get rid of either
			// "0.0" or
			// "1.0"
			if (doubles[i] == 1.0) {
				doubles[i] = 0.9999;
			}
		}

		// if the length of the doubles and chars would be equal the base of the
		// encoding
		// would always be the number of all poss. characters
		// as this is not always the case we have to find a different base
		int base = (int) Math.pow(NumberConverter.digits.length,
				(double) numberChars / doubles.length);

		// the number which results from the doubles if seen as one number
		// (doubles
		// -> number)
		int n = 0;

		for (int i = 0; i < doubles.length; i++) {
			n += ((int) (doubles[i] * base)) * Math.pow(base, i);
			// System.out.println(n + " " + base);
		}

		// the result
		char[] chars = new char[numberChars];

		// we want to encode with the given characters
		base = NumberConverter.digits.length;

		// encode the number with characters (number -> char)
		for (int i = 0; i < chars.length; i++) {
			chars[i] = NumberConverter.digits[n % base];
			n /= base;
			// System.out.println(chars[i] + " " + n);
		}

		return chars;
	}

	/**
	 * Decodes an array of chars to an array of doubles. It has to be known how
	 * many doubles are contained. (@see convertToChars)
	 * 
	 * @param chars -
	 *            the code to be decoded. Attention: this value shouldn't be
	 *            higher than 5 as higher values lead to wrong results because
	 *            of the limits for Integers.
	 * @param numberDoubles -
	 *            the number of contained double values
	 * @return an array of doubles
	 */
	public static double[] convertToDoubles(char[] chars, int start,
			int length, int numberDoubles) {

		// the chars are decoded on base = number of chars that can be used for
		// encoding
		int base = NumberConverter.digits.length;

		// the value of the "word" resulting from the chars
		int n = 0;

		for (int i = 0; i < length; i++) {
			n += NumberConverter.getIndexOf(chars[i + start])
					* Math.pow(base, i);
			// System.out.println(n + " " + base);
		}

		// result
		double[] doubles = new double[numberDoubles];

		// this undoes the encoding
		base = (int) Math.pow(NumberConverter.digits.length, (double) length
				/ numberDoubles);

		for (int i = 0; i < doubles.length; i++) {
			doubles[i] = (double) (n % base) / (base - 1);
			// System.out.println(n + " - " + base);
			n /= base;
		}

		return doubles;
	}

}
